<?php

/**
 * Costante che definisce il nome standard del campo ID delle tabelle nel DB.
 */
if(!defined("STD_ID"))
	define("STD_ID", "ID");

/**
 * Description of StdModel
 *
 * @author antosdr
 */
abstract class StdModel
{

	public $dataTupla;//[]

	/*
	 * Specifiche attributi del tipo del Model rappresentato da questa classe
	 */
	public $attribTableName;	//string Nome della tabella rappresentata nel DB.
	public $attribLabel;		//string Attributo etichetta di tutta la tupla.
	public $attribNames;		//string[] Nomi di tutti gli attributi.
	public $attribExcludeFromAdd;//string[] Nomi degli attributi da escludere dall'INSERT nel DB.
	public $attribExcludeFromEdit;//string[] Nomi degli attributi da escludere dall'UPDATE nel DB.
	public $attribLongNames;	//string[k=>v] Nomi lunghi degli attributi.
	public $attribIsImportant;	//int[k=>v] Importanza di ogni attributo.
	public $attribIsNumeric;	//boolean[k=>v] Se ogni attributo è numerico o no.
	public $attribIsForeignKey; //mixed[k=>v] Ogni attributo: false se non è chiave esterna, nome del model collegato se invece lo è.

	/**
	 * Costruttore standard di ogni componente del Model.
	 * @param array $data I dati passati da cui recuperare i valori.
	 */
	public function __construct($data)
	{
		$this->init($data);
	}

	/**
	 * Inizializza questa istanza con i valori passati.
	 * @param array $data I dati passati da cui recuperare i valori.
	 */
	protected final function init($data)
	{
		$this->dataTupla = array();
		if($data == NULL)
			return;
		foreach($this->attribNames as $attr)
		{
			if(isset($data[$attr]))
				$this->dataTupla[$attr] = $data[$attr];
			else
				$this->dataTupla[$attr] = NULL;
		}
	}

	/**
	 * Restituisce l'HTML per creare un campo di testo adatto a contenere il
	 * valore dell'attribbuto passato. Questo campo di testo sarà vuoto e dovrà
	 * servire per la creazione di una nuova istanza in DB di questo model.
	 * @param DatabaseManager $db Database su cui effettuare una possibile
	 * interrogazione.
	 * @param string $attribName Nome dell'attributo di cui creare il codice.
	 * @return string Il codice HTML dell'elemento form corrispondente.
	 */
	abstract public function getAddFormCodeFromAttribute($db, $attribName);

	/**
	 * Restituisce l'HTML per creare un campo di testo adatto a contenere il
	 * valore dell'attribbuto passato. Questo campo di testo conterrà il valore
	 * attualmente assunto da questo attributo in questa tupla.
	 * @param DatabaseManager $db Database su cui effettuare una possibile
	 * interrogazione.
	 * @param string $attribName Nome dell'attributo di cui creare il codice.
	 * @return string Il codice HTML dell'elemento form corrispondente.
	 */
	abstract public function getEditFormCodeFromAttribute($db, $attribName);

	/**
	 * Crea una nuova istanza della sottoclasse specializzante.
	 * @param array $data I dati passati da cui recuperare i valori.
	 * @return mixed Una nuova instanza della sottoclasse specializzante.
	 */
	abstract public function createNew($data);

	/**
	 * Aggiungi i valori di questa istanza al DB. L'ID di questa istanza
	 * assumerà il valore della chiave primaria della tupla appena inserita nel
	 * DB.
	 * @param DatabaseManager $db Database su cui effettuare l'INSERT.
	 * @return boolean Restituisce vero se l'INSERT è andato a buon fine, falso
	 * altrimenti.
	 */
	public function addToDB($db)
	{
		mysql_query("SET CHARACTER SET 'utf8'");
		$attrib = "";
		$values = "";
		$i = 0;
		foreach($this->attribNames as $attr)
		{
			if(in_array($attr, $this->attribExcludeFromAdd))
				continue;
			if($i > 0)
			{
				$attrib .= ", ";
				$values .= ", ";
			}
			$attrib .= DatabaseManager::EncodeFieldName($attr);
			$values .= DatabaseManager::PrintStringOrNullForQuery($this->dataTupla[$attr]);
			$i++;
		}
		
		if($db->AddRecord($this->attribTableName,$attrib,$values))
		{
			$this->dataTupla[STD_ID] = mysql_insert_id();
			return true;
		}
		return false;
	}

	/**
	 * Duplica i valori di una tupla, nel DB.
	 * Eventuali sottoclassi potrebbero implementare questo metodo per duplicare
	 * anche le associazioni NxN con un altra tabella.
	 * @param DatabaseManager $db Database su cui effettuare l'INSERT.
	 * @return mixed Restituisce l'istanza corrispondete alla nuova tupla se
	 * l'INSERT è andato a buon fine, NULL altrimenti.
	 */
	public function duplicateInDB($db)
	{
		mysql_query("SET CHARACTER SET 'utf8'");
		$instance = $this->getFromDB($db, $this->dataTupla[STD_ID]);
		if(!$instance)
			return NULL;
		return $instance->addToDB($db) ? $instance : NULL;
	}

	/**
	 * Rimuove dal DB la tupla rappresentata da questa instanza.
	 * @param DatabaseManager $db Database su cui effettuare il REMOVE.
	 * @return boolean Restituisce vero se il REMOVE è andato a buon fine, falso
	 * altrimenti.
	 */
	public function removeFromDB($db)
	{
		return $db->EraseRecords($this->attribTableName,STD_ID." = ".$this->dataTupla[STD_ID]);
	}

	/**
	 * Aggiorna I dati nel DB con quelli di questa istanza.
	 * @param DatabaseManager $db Database su cui effettuare l'UPDATE.
	 * @return boolean Restituisce vero se l'UPDATE è andato a buon fine, falso
	 * altrimenti.
	 */
	public function updateDB($db)
	{
		mysql_query("SET CHARACTER SET 'utf8'");
		$values = "";
		$i = 0;
		foreach($this->attribNames as $attr)
		{
			if(in_array($attr, $this->attribExcludeFromEdit))
				continue;
			if($i > 0)
			{
				$values .= ", ";
			}
			$values .= DatabaseManager::EncodeFieldName($attr) . " = " . DatabaseManager::PrintStringOrNullForQuery($this->dataTupla[$attr]);
			$i++;
		}
		return $db->UpdateRecord($this->attribTableName,$values,STD_ID." = ".$this->dataTupla[STD_ID]);
	}

	/**
	 * Carica una nuova istanza con i valori di una specifica tupla del DB.
	 * @param DatabaseManager $db Database da cui prelevare la tupla.
	 * @param long $id Identificatore univoco che indica la tupla da prelevare.
	 * @return mixed Ritorna la nuova istanza se il caricamento dei valori è
	 * andato a buon fine, NULL altrimenti.
	 */
	public function getFromDB($db, $id)
	{
		$newdata = $db->GetRecord($this->attribTableName,"*",STD_ID." = ".$id);
		if($newdata == NULL)
			return NULL;
		return $this->createNew($newdata);
	}

	/**
	 * Carica dal DB una lista di istanze di questa sottoclasse caricate con i
	 * valori delle tuple trovate secondo le specifiche passate.
	 * @param DatabaseManager $db Database da cui prelevare le tuple.
	 * @param int $limitMin Limite basso da cui iniziare a prelevare tuple, o
	 * NULL per non usare i limiti.
	 * @param int $limitMax Limite alto a cui fermarsi di prelevare tuple, o
	 * NULL per non usare i limiti.
	 * @param string $orderBy Ordinamento con cui prelevare le tuple, o "" per
	 * non usare alcun ordinamento.
	 * @return array L'array delle nuove istanze create a partire dai valori
	 * trovati nel DB o NULL nel caso qualcosa sia andato storto.
	 */
	public function getListFromDB($db, $limitMin, $limitMax, $orderBy, $cond = "")
	{
		$limits = ($limitMin === NULL || $limitMax === NULL ? "" : ($limitMin.", ".($limitMax-$limitMin)) );
		if($orderBy == NULL || $orderBy == "")
			$orderBy = STD_ID." DESC";
		$rs = $db->EnumerateTableStart($this->attribTableName,$orderBy,$cond,$limits);
		if($rs == FALSE)
			return NULL;
		$retarr = array();
		while($db->EnumerateTableNext($rs))
			$retarr[] = $this->createNew($db->EnumerateTableGetValues($rs));
		return $retarr;
	}

	/**
	 * Cerca una serie di tuple nel DB e ritorna tante istanze della sottoclasse
	 * specializzante, quante sono le rispettive tuple trovate con i rispettivi
	 * valori caricati entro i limiti specificati.
	 * @param DatabaseManager $db Database da cui prelevare le tuple.
	 * @param string $paramName Nome dell'attributo su cui effettuare la
	 * ricerca.
	 * @param string $paramValue Valore che l'attributo deve assumere per
	 * includere la sua rispettiva tupla.
	 * @param int $limitMin Limite basso da cui iniziare a prelevare tuple, o
	 * NULL per non usare i limiti.
	 * @param int $limitMax Limite alto a cui fermarsi di prelevare tuple, o
	 * NULL per non usare i limiti.
	 * @param string $orderBy Ordinamento con cui prelevare le tuple, o "" per
	 * non usare alcun ordinamento.
	 * @return array L'array delle nuove istanze create a partire dai valori
	 * trovati nel DB o NULL nel caso qualcosa sia andato storto.
	 */
	public function searchByParam($db, $paramName, $paramValue, $limitMin, $limitMax, $orderBy)
	{
		if(!isset($this->attribIsNumeric[$paramName]))
			return NULL;
		$limits = ($limitMin === NULL || $limitMax === NULL ? "" : ($limitMin.", ".($limitMax-$limitMin)) );
		$paramIsNumeric = $this->attribIsNumeric[$paramName];
		$rs = $db->EnumerateTableStart($this->attribTableName,$orderBy,
				($paramIsNumeric ? $paramName." = ".$paramValue : $paramName." LIKE '%".ModelUtils::escape_sq_for_SQL($paramValue)."%'"),
				$limits );
		if($rs == FALSE)
			return NULL;
		$retarr = array();
		while($db->EnumerateTableNext($rs))
			$retarr[] = $this->createNew($db->EnumerateTableGetValues($rs));
		return $retarr;
	}

	/**
	 * Restituiscce il numero di tuple di questa tabella.
	 * @param DatabaseManager $db Database da interrogare.
	 * @return int Restituisce il numero di tuple presenti o FALSE in caso di
	 * errore.
	 */
	public function getRecordsNum($db)
	{
		return $db->GetRecordsNum($this->attribTableName);
	}

	/**
	 * Restituiscce il numero di tuple del risultato della ricerca.
	 * @param DatabaseManager $db Database da interrogare.
	 * @param string $searchAttrib Nome dell'attributo su cui effettuare la
	 * ricerca.
	 * @param string $searchVal Valore che l'attributo deve assumere per far
	 * contare la propria tupla.
	 * @return int Restituisce il numero di tuple trovate o FALSE in caso di
	 * errore.
	 */
	public function getRecordsNumOfSearchResult($db,$searchAttrib,$searchVal)
	{
		if(!isset($this->attribIsNumeric[$searchAttrib]))
			return FALSE;
		$paramIsNumeric = $this->attribIsNumeric[$searchAttrib];
		$cond = ($paramIsNumeric ? $searchAttrib." = ".$searchVal : $searchAttrib." LIKE '%".ModelUtils::escape_sq_for_SQL($searchVal)."%'");
		return $db->GetRecordsNum($this->attribTableName,$cond);
	}

	/**
	 * Restituisce una stringa che rappresenta graficamente questa tupla nel DB.
	 * @return string La stringa etichetta di questa tupla.
	 */
	public function getLabel()
	{
		$str = "";
		if(is_array($this->attribLabel))
			foreach($this->attribLabel as $sl)
				$str .= $this->dataTupla[$sl]." ";
		else
			$str = $this->dataTupla[$this->attribLabel];
		if($this->cutDataLabels && strlen($str) > 70)
			return substr($str, 0, 70)."...";
		else
			return $str;
	}

	protected $cutDataLabels = true;

	/**
	 * Imposta una flag che determina se tagliare le stringhe lunghe o meno
	 * quando restituite con il metodo getDataLabel().
	 * @param boolean $enabled Se abilitare o meno la flag.
	 */
	public function cutLongStrings($enabled)
	{
		$this->cutDataLabels = $enabled;
	}

	/**
	 * Metodo che restituisce una etichetta che rappresenta il valore di un
	 * attributo: nella sua versione di default ritorna esattamente il valore
	 * associato a quell'attributo, ma è possibile implementarlo nelle
	 * sottoclassi per specificare opportuni comportamenti, come ad esempio di
	 * non far stampare un numero che rappresenta una chiave esterna, ma invece
	 * una etichetta della tupla ad essa associata.
	 * @param DatabaseManager $db Database su cui poter effettuare una possibile
	 * interrogazione.
	 * @param string $attrib Attributo di cui stampare l'etichetta del valore.
	 * @return string Il valore associato all'attributo o NULL se l'attributo
	 * non è stato trovato.
	 */
	public function getDataLabel($db, $attrib)
	{
		if(isset($this->dataTupla[$attrib]))
		{
			$str = $this->dataTupla[$attrib];
			if($this->cutDataLabels && strlen($str) > 70)
				return substr($str, 0, 70)."...";
			else
				return $str;
		}
		return NULL;
	}

	/**
	 * Restituisce vero se l'etichetta del valore dell'attributo passato viene
	 * tagliata a 70 caratteri, o falso se essa viene stampata per intero.
	 * @param string $attrib Attributo da controllare.
	 * @return boolean Vero se l'etichetta del valore viene taglata, falso
	 * altrimenti.
	 */
	public function isDataLabelCut($attrib)
	{
		if(isset($this->dataTupla[$attrib]))
		{
			if($this->cutDataLabels && strlen($this->dataTupla[$attrib]) > 70)
				return true;
		}
		return false;
	}

	protected $showRealValues_Enabled = false;

	/**
	 * Se abilitato, getDataLabel deve dare sempre i valori effettivi dei campi
	 * richiesti, anche nel caso delle chiave esterne in cui deve tornare il
	 * valore effettivo della chiave e non la sua etichetta esterna
	 * rappresentativa.
	 * @param boolean $enabled Se abilitare o no questa modalità.
	 */
	public function showRawValues($enabled)
	{
		$this->showRealValues_Enabled = $enabled;
	}

	/**
	 * Restituisce un array associativo [nome attributo => nome lungo
	 * attributo] contenente solo gli attributi ritenuti più importanti.
	 * @return array L'array generato.
	 */
	public function getImportantAttributesLongName()
	{
		$newarr = array();
		foreach($this->attribLongNames as $attr => $val)
			if($this->attribIsImportant[$attr] > 0)
				$newarr[$attr] = $val;
		return $newarr;
	}

	/**
	 * Restituisce gli attributi che danno maggiori informazioni di questa tupla
	 * come ad esempio gli attributi di un oggetto specificato da una chiave
	 * esterna.
	 * @return array L'array esteso di nomi di attributi.
	 */
	public function getExtendedInfoAttributeLongNames()
	{
		return $this->attribLongNames;
	}

	/**
	 * Restituisce gli attributi su cui è possibile effettuare una ricerca,
	 * nella forma array "nomeAttributo => nomeLungoAttributo".
	 * @return array L'array di attrubuti su cui è possibile effettuare una
	 * ricerca.
	 */
	public function getSearchableAttributes()
	{
		return $this->attribLongNames;
	}

	const PRINT_NOT_SUPPORTED = 0;
	const PRINT_FILE_FAILED = 1;
	const PRINT_DB_FAILED = 2;
	const PRINT_SUCCESS = 128;
	
	/**
	 * Stampa lo stream di byte di un file excel rappresentante la stampa del
	 * model di questa istanza.
	 * @param DatabaseManager $db Database su cui effettuare eventuali
	 * interrogazioni.
	 * @return int Ritorna il codice rappresentativo del successo o insuccesso
	 * dell'operazione.
	 */
	public function printExcelFile($db)
	{
		return self::PRINT_NOT_SUPPORTED;
		//Download di un file salvato in uno specifico percorso $file
		/**********************************************************************
		<?php
		if (file_exists($file)) {
			header('Content-Description: File Transfer');
			header('Content-Type: application/octet-stream');
			header('Content-Disposition: attachment; filename='.basename($file));
			header('Content-Transfer-Encoding: binary');
			header('Expires: 0');
			header('Cache-Control: must-revalidate');
			header('Pragma: public');
			header('Content-Length: ' . filesize($file));
			ob_clean();
			flush();
			readfile($file);
			exit;
		}
		?>
		*/
		//Download di un file il cui contenuto è memorizzato in una variabile
		//ad esempio $f = file_get_contents('downloads/track.mp3');
		/**********************************************************************
		header('Content-Description: File Transfer');
		header('Content-Type: application/octet-stream');
		header('Content-Disposition: attachment; filename='.basename($file));
		header('Content-Transfer-Encoding: binary');
		header('Expires: 0');
		header('Cache-Control: must-revalidate');
		header('Pragma: public');
		header('Content-Length: ' . filesize($file));
		ob_clean();
		flush();
		print $f;
		exit;
		*/
	}

	/**
	 * Mostra una pagina HTML rappresentante la stampa del model di questa
	 * istanza, che può essere facilmente stampata.
	 * @param DatabaseManager $db Database su cui effettuare eventuali
	 * interrogazioni.
	 * @return int Ritorna il codice rappresentativo del successo o insuccesso
	 * dell'operazione.
	 */
	public function printHTMLFile($db)
	{
		return self::PRINT_NOT_SUPPORTED;
	}

	const PRINT_UNAVAILABLE = 0;
	const PRINT_HTML_AVAILABLE = 1;//(1 << 0)
	const PRINT_EXCEL_AVAILABLE = 2;//(1 << 1)
	const PRINT_FULLY_AVAILABLE = 3;//(1 << 0) | (1 << 1)

	/**
	 * Restituisce una bitmask che rappresenta quali stampe supporta questo
	 * model.
	 * @return int La maschera di bit che rappresenta quali stampe sono
	 * supportate e quali no; PRINT_UNAVAILABLE viene restituito se questo model
	 * non supporta alcuna stampa.
	 */
	public function isPrintAvailable()
	{
		return self::PRINT_UNAVAILABLE;
	}

	private $lastPrintError;

	/**
	 * Impostabile solo da una sotto classe, questo metodo specifica il
	 * messaggio di errore dell'ultima stampa tentata.
	 * @param string $msg Messaggio di errore.
	 */
	protected function setLastPrintError($msg)
	{
		$this->lastPrintError = $msg;
	}

	/**
	 * Restituisce il messaggio di errore dell'ultima stampa tentata.
	 * @return string Il messaggio di errore.
	 */
	public function getLastPrintError()
	{
		return $this->lastPrintError;
	}

}

?>
